/* Copyright 2007-2008 dnAnalytics Project.
 *
 * Contributors to this file:
 * Patrick van der Velde
 *
 * This file is part of dnAnalytics.  dnAnalytics is licensed under the 
 * Microsoft Public License. See License.txt for a complete copy of the
 * license.
 */

using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Diagnostics;

namespace dnAnalytics.LinearAlgebra.Solvers
{
    /// <summary>
    /// An iterator that is used to check if an iterative calculation should continue or stop.
    /// </summary>
    public sealed class Iterator : IIterator
    {
        /// <summary>
        /// The default status for the iterator.
        /// </summary>
        private static readonly ICalculationStatus sm_DefaultStatus = new CalculationIndetermined();

        /// <summary>
        /// Creates a default iterator with all the <see cref="IIterationStopCriterium"/> objects.
        /// </summary>
        /// <returns>A new <c>IIterator</c> object.</returns>
        public static IIterator CreateDefault()
        {
            Iterator iterator = new Iterator();
            iterator.Add(new FailureStopCriterium());
            iterator.Add(new DivergenceStopCriterium());
            iterator.Add(new IterationCountStopCriterium());
            iterator.Add(new ResidualStopCriterium());

            return iterator;
        }

        /// <summary>
        /// The collection that holds all the stop criteria and the flag indicating if they should be added
        /// to the child iterators.
        /// </summary>
        private readonly Dictionary<Type, IIterationStopCriterium> m_StopCriteria = new Dictionary<Type, IIterationStopCriterium>();

        /// <summary>
        /// The status of the iterator.
        /// </summary>
        private ICalculationStatus m_Status = sm_DefaultStatus;

        /// <summary>
        /// Indicates if the iteration was cancelled.
        /// </summary>
        private bool m_WasIterationCancelled = false;

        // Constructors
        /// <summary>
        /// Creates an instance of the <c>Iterator</c> class.
        /// </summary>
        public Iterator() : this(null)
        { }

        /// <summary>
        /// Creates an instance of the <c>Iterator</c> class with the specified stop criteria.
        /// </summary>
        /// <param name="stopCriteria">
        /// The specified stop criteria. Only one stop criterium of each type can be passed in. None
        /// of the stop criteria will be passed on to child iterators.
        /// </param>
        /// <exception cref="DuplicateStopCriteriumException">
        ///   Thrown if <paramref name="stopCriteria"/> contains multiple stop criteria of the same type.
        /// </exception>
        public Iterator(IEnumerable<IIterationStopCriterium> stopCriteria)
        {
            // Add the stop criteria
            if (stopCriteria != null)
            {
                foreach (IIterationStopCriterium stopCriterium in stopCriteria)
                {
                    if (stopCriterium != null)
                    {
                        Add(stopCriterium);
                    }
                }
            }
        }

        /// <summary>
        /// Adds an <c>IIterationStopCriterium</c> to the internal collection of stop-criteria. Only a 
        /// single stop criterium of each type can be stored.
        /// </summary>
        /// <param name="stopCriterium">The stop criterium to add.</param>
        /// <exception cref="ArgumentNullException">
        ///   Thrown if <paramref name="stopCriterium"/> is null.
        /// </exception>
        /// <exception cref="DuplicateStopCriteriumException">
        ///   Thrown if <paramref name="stopCriterium"/> is of the same type as an already 
        ///   stored criterium.
        /// </exception>
        public void Add(IIterationStopCriterium stopCriterium)
        {
            if (stopCriterium == null)
            {
                throw new ArgumentNullException("stopCriterium");
            }

            if (m_StopCriteria.ContainsKey(stopCriterium.GetType()))
            {
                throw new DuplicateStopCriteriumException();
            }

            // Store the stop criterium.
            m_StopCriteria.Add(stopCriterium.GetType(), stopCriterium);
        }

        /// <summary>
        /// Removes the <c>IIterationStopCriterium</c> from the internal collection.
        /// </summary>
        /// <param name="stopCriterium">The stop criterium that must be removed.</param>
        public void Remove(IIterationStopCriterium stopCriterium)
        {
            if (stopCriterium == null)
            {
                throw new ArgumentNullException("stopCriterium");
            }

            if (!m_StopCriteria.ContainsKey(stopCriterium.GetType()))
            {
                return;
            }

            // Remove from the collection
            m_StopCriteria.Remove(stopCriterium.GetType());
        }

        /// <summary>
        /// Indicates if the specific stop criterium is stored by the <c>IIterator</c>.
        /// </summary>
        /// <param name="stopCriterium">The stop criterium.</param>
        /// <returns><c>true</c> if the <c>IIterator</c> contains the stop criterium; otherwise <c>false</c>.</returns>
        public bool Contains(IIterationStopCriterium stopCriterium)
        {
            if (stopCriterium == null)
            {
                return false;
            }

            if (m_StopCriteria.ContainsKey(stopCriterium.GetType()))
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Returns the number of stored stop criteria.
        /// </summary>
        /// <remarks>
        /// Used for testing only.
        /// </remarks>
        internal int NumberOfCriteria
        {
            get
            {
                return m_StopCriteria.Count;
            }
        }

        /// <summary>
        /// Returns an <c>IEnumerator</c> that enumerates over all the stored 
        /// stop criteria.
        /// </summary>
        /// <remarks>
        /// Used for testing only.
        /// </remarks>
        internal IEnumerator<IIterationStopCriterium> StoredStopCriteria
        {
            get
            {
                foreach (var criterium in m_StopCriteria)
                {
                    yield return criterium.Value;
                }
            }
        }

        /// <summary>
        /// Indicates to the iterator that the iterative process has been cancelled.
        /// </summary>
        /// <remarks>
        /// Does not reset the stop-criteria.
        /// </remarks>
        public void IterationCancelled()
        {
            m_WasIterationCancelled = true;
            m_Status = new CalculationCancelled();
        }

        /// <summary>
        /// Determines the status of the iterative calculation based on the stop criteria stored
        /// by the current <c>IIterator</c>.
        /// </summary>
        /// <param name="iterationNumber">The number of iterations that have passed so far.</param>
        /// <param name="solutionVector">The vector containing the current solution values.</param>
        /// <param name="sourceVector">The right hand side vector.</param>
        /// <param name="residualVector">The vector containing the current residual vectors.</param>
        /// <returns>
        ///   An <c>ICalculationStatus</c> which indicates what the status of the iterative 
        ///   calculation is according to the current <c>IIterator</c>.
        /// </returns>
        /// <remarks>
        /// The individual iterators may internally track the progress of the calculation based
        /// on the invocation of this method. Therefore this method should only be called if the 
        /// calculation has moved forwards at least one step.
        /// </remarks>
        public void DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector)
        {
            if (m_StopCriteria.Count == 0)
            {
                throw new StopCriteriumMissingException();
            }

            if (iterationNumber < 0)
            {
                throw new ArgumentOutOfRangeException("iterationNumber");
            }

            if (solutionVector == null)
            {
                throw new ArgumentNullException("solutionVector");
            }

            if (sourceVector == null)
            {
                throw new ArgumentNullException("sourceVector");
            }

            if (residualVector == null)
            {
                throw new ArgumentNullException("residualVector");
            }

            // While we're cancelled we don't call on the 
            // stop-criteria.
            if (m_WasIterationCancelled)
            {
                return;
            }

            foreach (KeyValuePair<Type, IIterationStopCriterium> pair in m_StopCriteria)
            {
                IIterationStopCriterium stopCriterium = pair.Value;

                stopCriterium.DetermineStatus(iterationNumber, solutionVector, sourceVector, residualVector);
                ICalculationStatus status = stopCriterium.Status;

                // Check if the status is:
                // - Running --> keep going
                // - Indetermined --> keep going
                // Anything else:
                // Stop looping and set that status
                if (!(status is CalculationRunning) && !(status is CalculationIndetermined))
                {
                    m_Status = status;
                    return;
                }
            }

            // Got all the way through
            // So we're running because we had vectors passed to us.
            if (!(m_Status is CalculationRunning))
            {
                m_Status = new CalculationRunning();
            }
        }

        /// <summary>
        /// Returns the current calculation status.
        /// </summary>
        public ICalculationStatus Status 
        { 
            get 
            { 
                return m_Status; 
            } 
        }

        /// <summary>
        /// Resets the <c>IIterator</c> to the pre-calculation state.
        /// </summary>
        public void ResetToPrecalculationState()
        {
            // Indicate that we're no longer cancelled.
            m_WasIterationCancelled = false;

            // Reset the status.
            m_Status = sm_DefaultStatus;
            // Reset the stop-criteria
            foreach (KeyValuePair<Type, IIterationStopCriterium> pair in m_StopCriteria)
            {
                IIterationStopCriterium stopCriterium = pair.Value;
                stopCriterium.ResetToPrecalculationState();
            }
        }

        /// <summary>
        /// Creates a deep clone of the current iterator.
        /// </summary>
        /// <returns>The deep clone of the current iterator.</returns>
        public IIterator Clone()
        {
            List<IIterationStopCriterium> stopCriteria = new List<IIterationStopCriterium>();
            foreach (KeyValuePair<Type, IIterationStopCriterium> pair in m_StopCriteria)
            {
                IIterationStopCriterium stopCriterium = pair.Value;
                stopCriteria.Add(stopCriterium.Clone());
            }

            return new Iterator(stopCriteria);
        }

        object ICloneable.Clone()
        {
            return Clone();
        }
    }
}
